WIP:WebSocket をスケールさせたい
問題
コネクション張りっぱなしになるので、1リクエスト1プロセスなどで処理してると限界がくる(C10K問題)
特にプロセスプールとか、スレッドプール使ってると、プールの最大値が同時接続数の最大値になる
プロセスはメモリ食うし、スレッドのスタック領域も意外とバカにならない
スタック領域:変数の値などが保存されている領域。他のスレッドから処理が戻ってきた時に、続きから再開させるのに必要になる。
参考:なぜ Go では何百万もの Goroutine を作れるのに Java は数千のスレッドしか作れないのか?
C10K問題
サーバーのハードウェア性能は問題ないにもかかわらず、クライアントの同時接続数が多くなるとサービスの応答が遅くなる問題
原因
コンテキストスイッチのコスト
プロセス数の上限
ファイルディスクリプタの上限
参考:いまさら聞けないNode.js
解決策
Reactor パターン(Node.js とか、Ruby の EventMachine とか)
CSP(Go の Goroutine とか)
アクターモデル(Elang とか、Java の Akka とか)
など
Reactor パターン
第29回 Reactorで非同期処理をやってみよう(1)
第32回 Reactorで非同期処理をやってみよう(2)
Reactor パターンを活かして WebSocket をスケールさせるには
「web サーバー側の通信周りでイベント待ちしてる処理」と「他ユーザーからのメッセージなどをイベント待ちしてる処理」とを同じイベントループで処理してやる必要がある。
「イベント待ちしてる処理」が非同期ではなく同期になってしまうと、イベントループがそこで待機してしまい、サーバー全体としても待機してしまう。
いわゆるブロッキング
ブロッキングせずにイベント待ちしたい
例えば、Redis の PubSub のイベント待ちしたい
例えば Ruby なら Async とか良さそう
HTTP サーバー、WebSocket、Redis のライブラリがある
ちょっと待って、やりすぎじゃない!?
ちゃんとしたリアルタイム通信が必要なら、ちゃんとした方がいい。
けど初めは全然想定がなくて、途中から「この情報だけリアルタイムで欲しい」みたいなこと言われるパターンありますよね
IO処理を全部 Reactor パターンにしろって言われても、今からじゃとてもできません。みたいな
僕がそうです。
なのでここからは「WebSocket を手軽にそこそこスケールさせたい」に改題してお送りいたします。
問題の振り返り
1台のサーバーで同時接続数1万も処理できなくてもいい
ダメでも台数増やせばいいし、増やすにしたって「100台追加!」とかするわけじゃない
プロセス数の上限
3万もあれば十分では?それに最近は 64 bit が主流になってるし大丈夫そう
ファイルディスクリプタの上限
DB接続などでコネクションプールとかを使っていれば大丈夫そう
コンテキストスイッチのコスト
スレッドの場合、 1~100 マイクロ秒くらい。
切替が平均 50 マイクロ秒で1万スレッドあったとしても一周するのに 0.5 秒
僕のお題は「他ユーザーのログイン状況をリアルタイムで知りたい」なので 0.5 秒ぐらいなんてことない
メモリ消費
Java だと1スレッドあたりのスタック領域は 1 MB ぐらいらしい。
1万スレッドで 10 GB か……結構いくな……
マルチプロセスの場合は、考えたくないね。
まとめ
C10K 問題は大丈夫そう。
C10K をクリアしてるのでスケールはするはず。
メモリ消費は残念そう。
Reactor パターンもイベントの待機列はできるし、スタック領域も保存する必要があるので、あんまり変わらない気もするのでベンチマークが欲しい
マルチプロセス/マルチスレッドでも大丈夫そう
ちゃんと負荷試験やろう
話の流れが良くなかった。この動画の流れを参考に書き直したい。